using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using needle.Services.astra.Collections;

namespace needle.Services.astra.Collections
{
	/// <summary>
	/// List which can be ordered when it or its items change.
	/// Items cannot be null.
	/// </summary>
	/// <typeparam name="T">Item type.</typeparam>
	public class OrderedList<T> : ItemsPropertyChangeList<T>
		where T : IComparable<T>, INotifyPropertyChanged
	{
		/// <summary>
		/// <see cref="Order"/> statuses.
		/// </summary>
		private enum OrderingStatuses
		{
			/// <summary>
			/// Not running.
			/// </summary>
			None,
			/// <summary>
			/// Running <see cref="DoOrder"/>.
			/// </summary>
			Running,
			/// <summary>
			/// Finishing.
			/// </summary>
			Finishing
		}


		#region Constructors

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="order">Whether to call <see cref="Order"/>.</param>
		/// <param name="items">List of items.</param>
		/// <exception cref="ArgumentException">
		/// One of <paramref name="items"/> is null.
		/// </exception>
		public OrderedList(bool order, params T[] items)
			: this(order, items as IList<T>)
		{
		}
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="order">Whether to call <see cref="Order"/>.</param>
		/// <param name="items">
		/// Generic list to be wrapped - its direct changes are not notified by <see cref="ItemsChange"/>.
		/// If it is not null and <see cref="List{T}"/>, it is used directly, otherwise new <see cref="List{T}"/> is created and used.
		/// </param>
		/// <exception cref="ArgumentException">
		/// One of <paramref name="items"/> is null.
		/// </exception>
		public OrderedList(bool order, IList<T> items)
			: base(items as List<T>, delegate(T item) { return !object.Equals(item, null); })
		{
			if (items != null && !(items is List<T>) && items.Count > 0)
				(this.Items as List<T>).AddRange(items);
			if (order)
				Order();
		}

		#endregion

		/// <summary>
		/// Whether list is ordered now.
		/// This is set to false when list or its items change.
		/// </summary>
		public bool IsOrdered
		{
			get { return _isOrdered; }
			protected set
			{
				if (_isOrdered == value)
					return;
				_isOrdered = value;
				OnPropertyChanged("IsOrdered");
			}
		}

		/// <summary>
		/// Orders list.
		/// </summary>
		public void Order()
		{
			if (this.IsOrdered)
				return;
			try
			{
				_orderingStatus = OrderingStatuses.Running;
				DoOrder();
				_orderingStatus = OrderingStatuses.Finishing;
				this.IsOrdered = true;
				OnItemsReset();
			}
			finally
			{
				_orderingStatus = OrderingStatuses.None;
			}
		}

		/// <summary>
		/// Does actual list ordering.
		/// </summary>
		/// <remarks>This implementation sorts list items.</remarks>
		protected virtual void DoOrder()
		{
			if (this.Count > 1)
				(this.Items as List<T>).Sort();
		}
		/// <summary>
		/// Called when an item fires <see cref="INotifyPropertyChanged.PropertyChanged"/> event.
		/// </summary>
		/// <remarks>
		/// This implementation sets <see cref="IsOrdered"/> to false and calls base.
		/// If <see cref="Order"/> is currently running, does nothing.
		/// </remarks>
		protected override void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (_orderingStatus == OrderingStatuses.Running)
				return;
			if (_orderingStatus != OrderingStatuses.Finishing)
				this.IsOrdered = false;
			base.OnItemPropertyChanged(sender, e);
		}
		/// <summary>
		/// Called when list changes.
		/// </summary>
		/// <remarks>
		/// This implementation sets <see cref="IsOrdered"/> to false and calls base.
		/// If <see cref="Order"/> is currently running, does nothing.
		/// </remarks>
		protected override void OnItemsChange(ListItemsChangeEventArgs<T> e)
		{
			if (_orderingStatus == OrderingStatuses.Running)
				return;
			if (_orderingStatus != OrderingStatuses.Finishing)
				this.IsOrdered = false;
			base.OnItemsChange(e);
		}

		/// <summary>
		/// See <see cref="IsOrdered"/>.
		/// </summary>
		private bool _isOrdered;
		/// <summary>
		/// <see cref="Order"/> status.
		/// Used in <see cref="OnItemPropertyChanged"/> and <see cref="OnItemsChange"/>.
		/// </summary>
		//[Transient]
		private OrderingStatuses _orderingStatus;
	}
}
